﻿package {
	import flash.display.Sprite;
	import flash.errors.IOError;
	import flash.events.Event;
	import flash.events.IOErrorEvent;
	import flash.events.ProgressEvent;
	import flash.events.SecurityErrorEvent;
	import flash.events.TimerEvent;
	import flash.net.Socket;
	import flash.sampler.clearSamples;
	import flash.sampler.DeleteObjectSample;
	import flash.sampler.getSampleCount;
	import flash.sampler.getSamples;
	import flash.sampler.NewObjectSample;
	import flash.sampler.Sample;
	import flash.sampler.StackFrame;
	import flash.sampler.startSampling;
	import flash.sampler.stopSampling;
	import flash.utils.ByteArray;
	import flash.utils.describeType;
	import flash.utils.getQualifiedClassName;
	import flash.utils.getQualifiedSuperclassName;
	import flash.utils.setTimeout;
	import flash.utils.Timer;
	public class Profiler extends Sprite {
		private static const PORT:int = 7125;
		
		private var connector:Timer = new Timer(500);
		private var socket:Socket;
		
		
		private var sampler:Timer = new Timer(100);
		
		private var self:Object;
		private var cumu:Object;
		private var sums:Object;
		private var prevTime:Number;
		private var raw:Array;
		
		public function Profiler():void {
			connector.addEventListener(TimerEvent.TIMER, connect);
			
			socket = new Socket();
			socket.addEventListener(ProgressEvent.SOCKET_DATA, socketData);
			socket.addEventListener(Event.CONNECT, connected);
			socket.addEventListener(IOErrorEvent.IO_ERROR, retry);
			socket.addEventListener(SecurityErrorEvent.SECURITY_ERROR, retry);
			socket.addEventListener(Event.CLOSE, disconnected);
			
			sampler.addEventListener(TimerEvent.TIMER, sampleNext);
			
			setTimeout(connect, 0);
			//connect();
		}
		
		private function retry(e:Event = null):void {
			connector.start();
		}
		
		private function connect(e:TimerEvent = null):void {
			connector.stop();
			try {
				socket.connect("127.0.0.1", PORT);
			} catch (e:Error) {
				retry();
			}
		}
		private function connected(e:Event):void {
			//socket.writeUTFBytes("sample start");
			//socket.flush();
			//startSampling();
			//sampler.start();
		}
		private function disconnected(e:Event):void {
			sampler.stop();
			retry();
		}
		
		private function socketData(e:ProgressEvent):void {
			var cmd:int = socket.readByte();
			switch (cmd) {
				case 1: startProfiling(); break;
				case 2: stopProfiling(); break;
				case 3: sendData(); break;
				case 4: sendRaw(); break;
			}
		}
		
		
		private function startProfiling():void {
			//socket.writeUTFBytes("started profiling");
			//socket.flush();
			sampler.start();
			self = { };
			cumu = { };
			sums = { };
			prevTime = -1;
			raw = [];
			startSampling();
		}
		private function stopProfiling():void {
			sampler.stop();
			stopSampling();
			//sums = null;
		}
		private function sendData():void {
			var bytes:ByteArray = new ByteArray();
			var tempBytes:ByteArray = new ByteArray();
			for (var func:String in sums) {
				bytes.writeDouble(self[func]);
				bytes.writeDouble(cumu[func]);
				bytes.writeDouble(sums[func]);
				tempBytes.writeUTFBytes(func);
				bytes.writeUnsignedInt(tempBytes.length);
				bytes.writeBytes(tempBytes);
				tempBytes.clear();
				/*
				var prevPos:uint = bytes.position;
				bytes.writeUnsignedInt(0);
				bytes.writeUTFBytes(func);
				var len:uint = bytes.position-prevPos;
				bytes.position = prevPos;
				bytes.writeUnsignedInt(len);
				bytes.position = bytes.length;
				*/
			}
			socket.writeByte(1);
			socket.writeUnsignedInt(bytes.length);
			socket.writeBytes(bytes);
			socket.flush();
		}
		private function sendRaw():void {
			socket.writeByte(2);
			var tempBytes:ByteArray = new ByteArray();
			tempBytes.writeObject(raw);
			socket.writeUnsignedInt(tempBytes.length);
			socket.writeBytes(tempBytes);
			tempBytes.clear();
			socket.flush();
		}

		private function sampleNext(e:TimerEvent):void {
			if (socket && socket.connected) {
				var samples:Object = getSamples();
				
				var sampleNum:int = 0;
				for each (var sample:Sample in samples) {
					var sfo:Array = [];
					if (sample.stack && prevTime != -1) {
						var stack:Array = sample.stack;
						for (var i:int = 0; i < stack.length; i++) {
							var sf:StackFrame = stack[i];
							//if (!(sample is NewObjectSample || sample is DeleteObjectSample)) {
							var fname:String = sf.name;
							if (!self.hasOwnProperty(sf.name)) {
								self[fname] = 0;
								cumu[fname] = 0;
								sums[fname] = 0;
							}
							if (i == 0) self[fname]++;
							cumu[fname]++;
							sums[fname] += sample.time-prevTime;
							//}
							
							if (sf.file) {
								sfo[i] = { "n": sf.name, "l": sf.line, "f": sf.file };
							} else {
								sfo[i] = { "n": sf.name };
							}
						}
					}
					if (sample is NewObjectSample) {
						var nosample:NewObjectSample = sample as NewObjectSample;
						raw.push( { "t": sample.time, "s": sfo, "nd": nosample.id, "c": getQualifiedClassName(nosample.type) } );
					} else if (sample is DeleteObjectSample) {
						var dosample:DeleteObjectSample = sample as DeleteObjectSample;
						raw.push( { "t": sample.time, "s": sfo, "dd": dosample.id, "z": dosample.size } );
					} else {
						raw.push( { "t": sample.time, "s": sfo } );
					}
					prevTime = sample.time;
					sampleNum++;
				}
				
				clearSamples();
				
				//socket.writeUTFBytes("samples "+getQualifiedClassName(samples)+" "+getQualifiedSuperclassName(samples)+" ");
				//for (var p:String in samples) {
					//socket.writeUTFBytes("sample >"+p+"< ");
					//socket.writeUTFBytes(p+" "+samples[p]);
				//}
				//for each (var s:Sample in samples) {
					//socket.writeUTFBytes("sample "+s.time); 
				//}
				//var msg:String = "";
				
				//msg += "s "+getSampleCount()+"\n";
				
				//var changedSums:Object = { };
				//stopProfiling();
				
				/*
				var tempBytes:ByteArray = new ByteArray();
				for (func in changedSums) {
					var sum:Number = sums[func];
					socket.writeDouble(sum);
					tempBytes.writeUTFBytes(func);
					socket.writeUnsignedInt(tempBytes.length);
					socket.writeBytes(tempBytes);
					tempBytes.clear();
					//msg += sum+" "+func+"\n";
				}
				
				socket.flush();
				//*/
			}
		}
	}
}